#pragma once
#include <cstdint>
#include <memory>
#include <array>
#include <functional>
#include <SFML/System/Vector2.hpp>
#include <SFML/Graphics/Color.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <nice2d/node.h>

using Quad = std::array<sf::Vertex, 4>;

struct Particle {
    explicit Particle(float lifetime = 10.0f):
        totalLifetime(lifetime)
    {
    }

    sf::Vector2f position;
    sf::Vector2f velocity;
    float rotation;
    float rotationSpeed;
    sf::Vector2f scale = sf::Vector2f(1.0f, 1.0f);
    sf::Vector2f startScale = sf::Vector2f(1.0f, 1.0f);
    sf::Color color = sf::Color::White;
    sf::Color startColor = sf::Color::White;
    uint32_t textureIndex = 0;
    float elapseLifetime = 0.0f;
    float totalLifetime;
};

class AbstractEmitter
{
public:
    virtual void emitParticle(const Particle &particle) = 0;
};

class ParticleEmitter
{
public:
    ParticleEmitter();
    virtual ~ParticleEmitter();
public:
    void setEmissionRate(float particlesPerSecond);
    void setParticleLifetime(std::function<float()> particleLifetime);
    void setParticlePosition(std::function<sf::Vector2f()> particlePosition);
    void setParticleVelocity(std::function<sf::Vector2f()> particleVelocity);
    void setParticleAccelerate(std::function<sf::Vector2f()> particleAccelate);
    void setParticleRotation(std::function<float()> particleRotation);
    void setParticleRotationSpeed(std::function<float()> particleRotationSpeed);
    void setParticleScale(std::function<sf::Vector2f()> particleScale);
    void setParticleColor(std::function<sf::Color()> particleColor);
    void setParticleTextureIndex(std::function<uint32_t()> particleTextureIndex);

    void operator() (AbstractEmitter *emitter, float deltaTime);
private:
    std::unique_ptr<class ParticleEmitterData> data;
};

class ParticleAffector
{
public:
    virtual void operator()(Particle &particle, float deltaTime) = 0;
};

class ParticleForceAffector : public ParticleAffector
{
public:
    ParticleForceAffector() = delete;
    explicit ParticleForceAffector(sf::Vector2f inputAcceleration);
public:
    void operator() (Particle &particle, float deltaTime) override;
private:
    sf::Vector2f acceleration;
};

class ParticleTorqueAffector : public ParticleAffector
{
public:
    ParticleTorqueAffector() = delete;
    explicit ParticleTorqueAffector(float inputAngularAcceleration);
public:
    void operator() (Particle &particle, float deltaTime) override;
private:
    float angularAcceleration;
};

class ParticleScaleAffector : public ParticleAffector
{
public:
    ParticleScaleAffector() = delete;
    explicit ParticleScaleAffector(sf::Vector2f inputScaleFactor);
public:
    void operator() (Particle &particle, float deltaTime) override;
private:
    sf::Vector2f scaleFactor;
};

class ParticleAlphaAffector : public ParticleAffector
{
public:
    ParticleAlphaAffector() = delete;
    explicit ParticleAlphaAffector(float inputAlpha);
public:
    void operator() (Particle &particle, float deltaTime) override;
private:
    float alpha;
};

class ParticleColorAffector : public ParticleAffector
{
public:
    ParticleColorAffector() = delete;
    explicit ParticleColorAffector(const sf::Color &inputColor);
public:
    void operator()(Particle &particle, float deltaTime) override;
private:
    sf::Color color;
    float percent = 0.0f;
};

class ParticleProxyAffector : public ParticleAffector
{
public:
    ParticleProxyAffector() = delete;
    explicit ParticleProxyAffector(std::function<void(Particle &, float)> inputFunction);
public:
    void operator() (Particle &particle, float deltaTime) override;
private:
    std::function <void(Particle &, float)> function;
};

class ParticleSystem : public Node, public AbstractEmitter
{
public:
    ParticleSystem();
    ~ParticleSystem();
public:
    void setTexture(sf::Texture *texture);
    uint32_t addTextureRect(const sf::IntRect &textureRect);

    void setParticleEmitter(std::shared_ptr<ParticleEmitter> emitter);
    std::shared_ptr<ParticleEmitter> getParticleEmitter()const;

    void addAffector(std::shared_ptr<ParticleAffector> affector);
    void clearAffectors();

    void clearParticles();
public:
    void onDrawNode(sf::RenderTarget &target, sf::RenderStates states) const override;
    void onUpdateNode(float deltaTime) override;
    void emitParticle(const Particle &particle) override;
private:
    std::unique_ptr<class ParticleSystemData> data;
};
